package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.heima.article.dtos.HotArticleDto;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.pojos.ApArticle;
import com.heima.article.service.HotArticleService;
import com.heima.common.constants.ArticleConstants;
import com.heima.common.exception.CustomException;
import com.heima.common.redis.CacheService;
import com.heima.feign.wemedia.WemediaClient;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.ArticleVisitStreamMess;
import com.heima.model.mess.UpdateArticleMess;
import com.heima.wemedia.pojos.WmChannel;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class HotArticleServiceImpl implements HotArticleService {

    @Autowired
    private ApArticleMapper apArticleMapper;

    @Autowired
    private WemediaClient wemediaClient;

    @Autowired
    private CacheService cacheService;

    @Override
    public void computeHotArticle() {
        // 查询近5天文章
        List<ApArticle> apArticles = apArticleMapper.selectArticleByDays(4000);//模拟查询近10年发布的文章
        //没有文章直接结束
        if (apArticles == null || apArticles.size() == 0) {
            return;
        }
        // 计算出每一篇文章的分值(阅读数*1 点赞数*3 评论数*5 收藏数*8)
        List<HotArticleDto> hotArticleDtos = computeHotArticle(apArticles);
        System.out.println("hotArticleDtos = " + hotArticleDtos);

        // 将每个频道 和 推荐频道,取出前30条分值较高的文章存入redis
        //1 将各个频道,取出前30条分值较高的文章存入redis
        //远程调用查询出所有频道
        ResponseResult<List<WmChannel>> result = wemediaClient.channelList();
        if (result.getCode() != AppHttpCodeEnum.SUCCESS.getCode()) {
            throw new CustomException(AppHttpCodeEnum.CALL_INTERFACE_FAIL);
        }
        List<WmChannel> wmChannelList = result.getData();
        //遍历计所有分值的文章,将相应的频道的文章,存入各自的集合中
        saveChannelHotArticle(hotArticleDtos, wmChannelList);
    }

    /**
     * 计算热点文章分值(阅读数*1 点赞数*3 评论数*5 收藏数*8)
     *
     * @param apArticles 文章信息
     * @return 返回热点文章
     */
    private List<HotArticleDto> computeHotArticle(List<ApArticle> apArticles) {
        List<HotArticleDto> hotArticleDtos = apArticles.stream().map(
                apArticle -> {
                    HotArticleDto hotArticleDto = computeScore(apArticle);
                    return hotArticleDto;
                }
        ).collect(Collectors.toList());
        return hotArticleDtos;
    }

    /**
     * 计算每个热点文章分值
     *
     * @param apArticle 文章信息
     * @return 返回结果
     */
    private HotArticleDto computeScore(ApArticle apArticle) {
        HotArticleDto hotArticleDto = new HotArticleDto();
        int views = apArticle.getViews() == null ? 0 : apArticle.getViews();
        int likes = apArticle.getLikes() == null ? 0 : apArticle.getLikes();
        int comments = apArticle.getComment() == null ? 0 : apArticle.getComment();
        int collections = apArticle.getCollection() == null ? 0 : apArticle.getCollection();
        //计算每一篇分值
        int score = views * 1 + likes * 3 + comments * 5 + collections * 8;
        hotArticleDto.setScore(score);
        BeanUtils.copyProperties(apArticle, hotArticleDto);
        return hotArticleDto;
    }

    /**
     * 存放每个频道的热点文章-存入redis
     *
     * @param hotArticleDtos 热点文章数据
     * @param wmChannelList  文章频道
     */
    private void saveChannelHotArticle(List<HotArticleDto> hotArticleDtos, List<WmChannel> wmChannelList) {
        for (WmChannel wmChannel : wmChannelList) {
            //存放每个频道的热点文章
            List<HotArticleDto> wmChannelHotArticle = new ArrayList<HotArticleDto>();
            for (HotArticleDto hotArticleDto : hotArticleDtos) {
                if (hotArticleDto.getChannelId() == wmChannel.getId()) {
                    wmChannelHotArticle.add(hotArticleDto);
                }
            }
            //将每个频的热点文章存入redis中
            //redis的key的设计:业务前缀+频道id
            String wmChannelKey = ArticleConstants.HOT_ARTICLE_FIRST_PAGE + wmChannel.getId();
            sortAndSave(wmChannelKey, wmChannelHotArticle);
        }

        //2 将推荐频道,取出前30条分值较高的文章存入redis
        String allKey = ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG;
        sortAndSave(allKey, hotArticleDtos);
    }

    /**
     * 将计算出的<strong>各个频道热点文章</strong>和<strong>推荐频道的热点文章</strong> <br>排序和存入redis中
     *
     * @param redisKey          设计redis的key
     * @param hotArticleDtoList 热点文章
     */
    private void sortAndSave(String redisKey, List<HotArticleDto> hotArticleDtoList) {
        // 将每个频道的热点文章,按照分值升序排序
        Collections.sort(hotArticleDtoList, new Comparator<HotArticleDto>() {
            @Override
            public int compare(HotArticleDto o1, HotArticleDto o2) {
                return o2.getScore() - o1.getScore();
            }
        });
        //判断文章数量是否>30
        if (hotArticleDtoList.size() >= 30) {
            //如果文章>=30 取出前30条数据存入redis
            hotArticleDtoList = hotArticleDtoList
                    .stream()
                    .limit(30)
                    .collect(Collectors.toList());

            cacheService.set(redisKey, JSON.toJSONString(hotArticleDtoList));
        } else {
            // 如果文章没有>=30 直接存入数据库里
            cacheService.set(redisKey, JSON.toJSONString(hotArticleDtoList));
        }
    }

    /**
     * 更新热点文章数据和数据库中文章行为数量
     *
     * @param message 接收消息
     */
    @Override
    public void updateHotArticle(String message) {
        //转换类型 TODO
        ArticleVisitStreamMess mess = JSON.parseObject(message, ArticleVisitStreamMess.class);

        long articleId = mess.getArticleId();
        //更新数据库中文章行为数量
        ApArticle apArticle = apArticleMapper.selectById(articleId);
        int views = apArticle.getViews() == null ? 0 : apArticle.getViews();
        int likes = apArticle.getLikes() == null ? 0 : apArticle.getLikes();
        int comments = apArticle.getComment() == null ? 0 : apArticle.getComment();
        int collections = apArticle.getCollection() == null ? 0 : apArticle.getCollection();
        apArticle.setViews(views + mess.getView());
        apArticle.setLikes(likes + mess.getLike());
        apArticle.setComment(comments + mess.getComment());
        apArticle.setCollection(collections + mess.getCollect());
        apArticleMapper.updateById(apArticle);

        //更新redis热点文章数据
        //1 更新推荐频道热点数据
        updateHotArticleCacheData(articleId, apArticle, ArticleConstants.DEFAULT_TAG);


        //2 更新其他具体频道热点数据
        updateHotArticleCacheData(articleId, apArticle, apArticle.getChannelId().toString());
    }

    /**
     * 更新redis中热点文章缓存数据
     *
     * @param articleId 文章id(判断当前频道中的文章)
     * @param apArticle 文章信息(统计点赞 收藏 阅读 评论之后的文章)
     */
    private void updateHotArticleCacheData(long articleId, ApArticle apArticle, String key) {
        //获取推荐频道或其它具体频道热点文章数据
        String hotArticleValue =
                cacheService.get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + key);
        List<HotArticleDto> hotArticles =
                JSON.parseArray(hotArticleValue, HotArticleDto.class);
        //计算当前文章的热点数据
        HotArticleDto hotArticleDto = computeScore(apArticle);
        if (hotArticles.size() != 0) {
            //1 判断推荐频道或其它具体频道中是否有当前热点文章(false没有 true有)
            boolean flag = false;
            for (HotArticleDto hotArticle : hotArticles) {
                //1.1 如果有就更新推荐频道中的热点文章(更新分数和redis)
                if (hotArticle.getId() == articleId) {
                    flag = true;
                    //1.1.1 更新分数 点赞 阅读 评论 阅读数量
                    saveOrUpdateHotArticleDto(apArticle, hotArticleDto.getScore() * 3, hotArticle);
                    //找出旧的热点文章直接结束,并更新这篇热点文章
                    break;
                }
            }
            //1.1.2 更新redis
            if (flag) {
                hotArticles.sort((o1, o2) -> o2.getScore() - o1.getScore());
                cacheService.set(
                        ArticleConstants.HOT_ARTICLE_FIRST_PAGE + key,
                        JSON.toJSONString(hotArticles)
                );
                return;
            }
            //2 判断推荐频道的文章数量是否>=30
            if (hotArticles.size() >= 30) {
                // 2.1如果>=30,就更新redis中最后一条数据
                //获取频道列表的最后一条,热点文章
                HotArticleDto hotArticle = hotArticles.get(hotArticles.size() - 1);
                //移除并更新频道列表最后一条文章数据(更新分数 点赞 阅读 评论 阅读数量)
                hotArticles.remove(hotArticles.size() - 1);
                int hotArticleScore = hotArticleDto.getScore() * 3;
                saveOrUpdateHotArticleDto(apArticle, hotArticleDto.getScore() * 3, hotArticle);
                hotArticles.add(hotArticleDto);
                hotArticles.sort((o1, o2) -> o2.getScore() - o1.getScore());
                cacheService.set(
                        ArticleConstants.HOT_ARTICLE_FIRST_PAGE + key,
                        JSON.toJSONString(hotArticles)
                );
            }
            //2.2 如果<30,直接将热点文章添加到redis中
            if (hotArticles.size() < 30) {
                HotArticleDto hotArticle = new HotArticleDto();
                saveOrUpdateHotArticleDto(apArticle, hotArticleDto.getScore() * 3, hotArticle);
                hotArticles.add(hotArticle);
                hotArticles.sort((o1, o2) -> o2.getScore() - o1.getScore());
                cacheService.set(
                        ArticleConstants.HOT_ARTICLE_FIRST_PAGE + key,
                        JSON.toJSONString(hotArticles)
                );
            }
        } else {
            //3 如果频道中的热点文章为null,直接加入热点文章
            hotArticles = new ArrayList<>();
            hotArticleDto.setScore(hotArticleDto.getScore() * 3);
            hotArticles.add(hotArticleDto);
            hotArticles.sort((o1, o2) -> o2.getScore() - o1.getScore());
            cacheService.set(
                    ArticleConstants.HOT_ARTICLE_FIRST_PAGE + key,
                    JSON.toJSONString(hotArticles)
            );
        }
    }

    /**
     * 保存或更新热点文章数据
     *
     * @param apArticle              原文章信息
     * @param hotArticleArticleScore 获取最新的文章分值信息
     * @param hotArticle             保存或更新的热点文章数据
     */
    private void saveOrUpdateHotArticleDto(ApArticle apArticle, int hotArticleArticleScore, HotArticleDto hotArticle) {
        hotArticle.setScore(hotArticleArticleScore);
        hotArticle.setLikes(apArticle.getLikes());
        hotArticle.setCollection(apArticle.getCollection());
        hotArticle.setComment(apArticle.getComment());
        hotArticle.setViews(apArticle.getViews());
    }
}
